/**
 * ****************************************************************************
 * Project: RoboBridge
 * ----------------------------------------------------------------------------
 * -------------------------- [c]2012 - KrawallMann ---------------------------
 * ---------------------- http://www.krawallmann.de.tc/ -----------------------
 * ****************************************************************************
 * File: AbstractProtocol.java Package:
 * tc.de.krawallmann.robobridge.communication.protocol Version: 1.0 Author(s):
 * KrawallMann
 * ****************************************************************************
 * Description:
 * ****************************************************************************
 */
package tc.de.krawallmann.robobridge.communication.protocol;

import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

import tc.de.krawallmann.robobridge.communication.AbstractCommunicationInterface;
import tc.de.krawallmann.robobridge.communication.AbstractRemoteCommunicationDevice;
import tc.de.krawallmann.robobridge.communication.Command;
import tc.de.krawallmann.robobridge.communication.CommandType;
import tc.de.krawallmann.robobridge.communication.IncomingCommandListener;
import tc.de.krawallmann.robobridge.communication.IncomingMessageListener;
import tc.de.krawallmann.robobridge.util.Log;

/**
 * The Class AbstractProtocol is an abstraction of a protocol.
 * 
 * @author KrawallMann
 * @version 1.0
 */
public abstract class AbstractProtocol implements IncomingMessageListener {

	/** The communication interface. */
	protected transient final AbstractCommunicationInterface<? extends AbstractRemoteCommunicationDevice> communicationInterface;

	/** The ack received. */
	private transient boolean ackReceived;

	/** The awaiting ack. */
	private transient Command awaitingAck;

	private transient boolean awaitsAck;

	/** The listeners. */
	private transient final Set<IncomingCommandListener> listeners =
			new HashSet<IncomingCommandListener>();

	/**
	 * Instantiates a new abstract protocol.
	 * 
	 * @param communicationInterface
	 *            the communication interface
	 */
	public AbstractProtocol(
			final AbstractCommunicationInterface<? extends AbstractRemoteCommunicationDevice> communicationInterface) {
		super();
		this.communicationInterface = communicationInterface;
		communicationInterface.getReader().addIncomingMessageListener(this);
	}

	/**
	 * Adds the incoming command listener.
	 * 
	 * @param listener
	 *            the listener
	 * @return true, if successful
	 */
	public boolean addIncomingCommandListener(final IncomingCommandListener listener) {
		return listeners.add(listener);
	}

	/**
	 * Clear incoming command listeners.
	 */
	public void clearIncomingCommandListeners() {
		listeners.clear();
	}

	/**
	 * Close.
	 */
	public abstract void close();

	/**
	 * Gets the communication interface.
	 * 
	 * @return the communication interface
	 */
	public AbstractCommunicationInterface<? extends AbstractRemoteCommunicationDevice> getCommunicationInterface() {
		return communicationInterface;
	}

	/**
	 * Gets the incoming command listener count.
	 * 
	 * @return the incoming command listener count
	 */
	public int getIncomingCommandListenerCount() {
		return listeners.size();
	}

	/**
	 * Gets the state.
	 * 
	 * @return the state
	 */
	public abstract AbstractProtocolState<? extends AbstractProtocol> getState();

	/*
	 * (non-Javadoc)
	 * @see
	 * communication.IncomingMessageListener#onMessageReceived(java.lang.String)
	 */
	@Override
	public void onMessageReceived(final String message) {
		try {
			final Command command = Command.parse(message);
			checkAck(command);
			for (final IncomingCommandListener l : listeners) {
				l.onCommandReceived(command);
			}
		} catch (final Exception exc) {
			exc.printStackTrace();
			/*Log.log(Level.WARNING, getClass(), "onMessageReceived", "Exception caught - {0}!",
					exc.getMessage());*/
		}
	}

	/**
	 * Open.
	 */
	public abstract void open();

	/**
	 * Removes the incoming command listener.
	 * 
	 * @param listener
	 *            the listener
	 * @return true, if successful
	 */
	public boolean removeIncomingCommandListener(final IncomingCommandListener listener) {
		return listeners.remove(listener);
	}

	/**
	 * Send command.
	 * 
	 * @param command
	 *            the command
	 * @throws InterruptedException
	 *             the interrupted exception
	 */
	public void sendCommand(final Command command) throws InterruptedException {
		communicationInterface.getWriter().write(command.serialize());
		waitForAck(command);
	}

	/**
	 * Update.
	 */
	public abstract void update();

	/**
	 * Check ack.
	 * 
	 * @param command
	 *            the command
	 */
	protected void checkAck(final Command command) {
		if (awaitsAck && command.getType() == CommandType.ACK) {
			Log.log(Level.INFO, getClass(), "checkAck", "Received ACK: {0}, awaited: {1}!",
					CommandType.get((Integer) command.getParameter(0)), awaitingAck.getType());
			ackReceived = true;
			synchronized (awaitingAck) {
				awaitingAck.notifyAll();
			}
			awaitsAck = false;
		}
	}

	/**
	 * Wait for ack.
	 * 
	 * @param command
	 *            the command
	 * @throws InterruptedException
	 *             the interrupted exception
	 */
	protected void waitForAck(final Command command) throws InterruptedException {
		if (command.isAckRequired()) {
			Log.log(Level.INFO, getClass(), "waitForAck",
					"This command requires ACK: {0} - waiting for the robot!", command);
			synchronized (this) {
				awaitingAck = command;
				awaitsAck = true;
			}
			synchronized (awaitingAck) {
				awaitingAck.wait(1000);
				if (!ackReceived) {
					throw new InterruptedException("Awaiting ACK: Timeout!");
				}
				ackReceived = false;
			}
		}
	}
}
